#pragma once

/*
    该模块用于实现数据的交互，并对外提供访问数据的接口
    将题库加载到内存中
    
    通过条件编译，实现了两个不同的版本：文件版和MySQL版
*/

//#include "../common/log.hpp"
#include "../RPC/source/common/log.hpp"
#include "../common/util.hpp"
#include <unordered_map>
#include <vector>
#include <string>
#include <cassert>
#include <fstream>
#include <mysql/mysql.h>
#include <unistd.h>

namespace oj_model
{
    //using namespace oj_log;
    using namespace oj_util;

    const std::string default_questions_list_path = "./questions/questions.list";
    const std::string default_questions_path = "./questions/";

    // 题目属性
    struct Question
    {
        std::string number; // 题目编号
        std::string title;  // 题目标题
        std::string level;  // 题目难度
        int cpu_limit;      // CPU限制
        int mem_limit;      // 内存限制
        std::string desc;   // 题目描述
        std::string header; // 前置代码
        std::string tail;   // 测试用例
    };

//MySQL版
#ifdef __MYSQL_VERSION__

    const std::string database = "oj";
    const std::string table = "oj_questions";
    const std::string host = "127.0.0.1";
    const std::string user = "oj_client";
    const std::string passward = "123456";
    const std::string port = "3306";

    class Model
    {
    private:
        // 对连接的MySQL发送指令
        bool MySQLQuery(const std::string &sql, std::vector<Question> &result)
        {
            // 0. 创建MySQL句柄
            MYSQL *mysql;
            mysql = mysql_init(nullptr);

            // 1. 连接MySQL
            mysql_real_connect(mysql, host.c_str(), user.c_str(), passward.c_str(),
                               database.c_str(), std::stoi(port), nullptr, 0);

            LOG(JsonMuduoRPC::INFO, "数据库连接成功");

            // 2. 设置编码格式
            mysql_set_character_set(mysql, "utf8"); // 设置编码格式为utf8

            // 3. 对MySQL下达指令
            int ret = mysql_query(mysql, sql.c_str());
            if (ret != 0)
            {
                LOG(JsonMuduoRPC::ERROR, "SQL执行失败");
                return false;
            }

            // 4. 获取结果
            MYSQL_RES *res = mysql_store_result(mysql);
            if (nullptr == res)
            {
                LOG(JsonMuduoRPC::ERROR, "SQL执行结果获取失败");
                return false;
            }

            // 4.1 获取行列
            int row = mysql_num_rows(res);
            int col = mysql_num_fields(res);

            // 4.2 遍历结果，进行解析
            MYSQL_ROW line;
            for (int i = 0; i < row; i++)
            {
                line = mysql_fetch_row(res);

                Question q;
                q.number = line[0];
                q.title = line[1];
                q.level = line[2];
                q.desc = line[3];
                q.header = line[4];
                q.tail = line[5];
                q.cpu_limit = std::stoi(line[6]);
                q.mem_limit = std::stoi(line[7]);

                result.push_back(q);
            }

            mysql_free_result(res);
            mysql_close(mysql);

            return true;
        }

        /*
            这个函数只需要在需要向数据库新增题目的时候使用
            加载题目列表和对应的题目属性加载到MySQL
            加载之前要先将表之前的数据清空
        */
        bool loadQuestionList(const std::string &questions_list_path)
        {
            // 0. 创建MySQL句柄
            MYSQL *mysql;
            mysql = mysql_init(nullptr);
            if (mysql == nullptr)
            {
                LOG(JsonMuduoRPC::FATAL, "数据库初始化失败");
                return false;
            }

            // 1. 连接MySQL
            mysql_real_connect(mysql, host.c_str(), user.c_str(), passward.c_str(),
                               database.c_str(), std::stoi(port), nullptr, 0);

            LOG(JsonMuduoRPC::INFO, "数据库连接成功");

            // 2. 设置编码格式
            mysql_set_character_set(mysql, "utf8"); // 设置编码格式为utf8

            std::ifstream in(questions_list_path);
            if (in.is_open() == false)
            {
                LOG(JsonMuduoRPC::FATAL, "题目列表文件打开失败");
                return false;
            }

            // 清空表内容
            // 注意空格！！！
            std::string sql = "truncate " + table;
            int ret = mysql_query(mysql, sql.c_str());
            if (ret != 0)
            {
                LOG(JsonMuduoRPC::ERROR, "数据清空失败");
                return false;
            }

            // 按行读取配置文件
            std::string line;
            int count = 0;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::spiltStr(line, tokens, " ");
                ++count;

                // 1    判断回文数     简单    1    30000
                if (tokens.size() != 5)
                {
                    LOG(JsonMuduoRPC::WARNING, "题目: %d, 配置文件格式错误", count);
                    continue;
                }

                // 填写对应题目的属性
                Question q;
                q.number = tokens[0];
                q.title = tokens[1];
                q.level = tokens[2];
                q.cpu_limit = std::stoi(tokens[3]);
                q.mem_limit = std::stoi(tokens[4]);

                std::string question_path = default_questions_path + q.number + '/';
                FileUtil::readFile(question_path + "desc.txt", q.desc, true);
                FileUtil::readFile(question_path + "header.cpp", q.header, true);
                FileUtil::readFile(question_path + "tail.cpp", q.tail, true);

                // 将题目信息写入MySQL表
                char sql[4096] = {0};
                snprintf(sql, sizeof(sql) - 1, "insert into %s values (%d, '%s', '%s', '%s', '%s', '%s', %d, %d)",
                         table.c_str(), std::stoi(q.number), q.title.c_str(), q.level.c_str(), q.desc.c_str(), q.header.c_str(), q.tail.c_str(), q.cpu_limit, q.mem_limit);

                // 对MySQL下达指令
                int ret = mysql_query(mysql, sql);
                if (ret != 0)
                {
                    LOG(JsonMuduoRPC::ERROR, "插入语句执行失败");
                    return false;
                }
            }

            in.close();
            mysql_close(mysql);

            LOG(JsonMuduoRPC::INFO, "MySQL题库加载成功");
            return true;
        }

    public:
#ifdef __RELOAD_DATA__
        //只有在编译时加上了__RELOAD_DATA__
        //表示要重新加载题目，才会只执行loadQuestionList
        Model(const std::string &questions_list_path = default_questions_list_path)
        {
            assert(loadQuestionList(questions_list_path));
        }
#endif

        // 获取所有题目
        bool getAllQuestions(std::vector<Question> &questions)
        {
            std::string sql = "select * from " + table;
            bool ret = MySQLQuery(sql, questions);
            if (ret == false || questions.size() == 0)
                return false;

            return true;
        }

        // 获取指定题目
        bool getOneQuestion(const std::string &number, Question &question)
        {
            std::string sql = "select * from " + table + " where number = " + number;
            std::vector<Question> res;
            bool ret = MySQLQuery(sql, res);
            if (ret == false || res.size() != 1)
                return false;

            question = res[0];
            return true;
        }
    };
#endif

//文件版
#ifdef __FILE_VERSION__

    class Model
    {
    public:
        Model(const std::string &questions_list_path = default_questions_list_path)
        {
            assert(loadQuestionList(questions_list_path));
        }

        // 获取所有题目
        bool getAllQuestions(std::vector<Question> &questions)
        {
            if (_questions_map.empty())
            {
                LOG(JsonMuduoRPC::ERROR, "当前没有题目可供获取");
                return false;
            }

            for (const auto &question : _questions_map)
                questions.push_back(question.second);

            return true;
        }

        // 获取指定题目
        bool getOneQuestion(const std::string &number, Question &question)
        {
            const auto &it = _questions_map.find(number);
            if (it == _questions_map.end())
            {
                LOG(JsonMuduoRPC::ERROR, "不存在题目: %d", number);
                return false;
            }

            question = it->second;
            return true;
        }

    private:
        // 加载题目列表和对应的题目属性加载到_questions_map
        bool loadQuestionList(const std::string &questions_list_path)
        {
            std::ifstream in(questions_list_path);
            if (in.is_open() == false)
            {
                LOG(JsonMuduoRPC::FATAL, "题目列表文件打开失败");
                return false;
            }

            // 按行读取配置文件
            std::string line;
            int count = 0;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::spiltStr(line, tokens, " ");
                ++count;

                // 1    判断回文数     简单    1    30000
                if (tokens.size() != 5)
                {
                    LOG(JsonMuduoRPC::WARNING, "题目: %d, 配置文件格式错误", count);
                    continue;
                }

                // 填写对应题目的属性
                Question q;
                q.number = tokens[0];
                q.title = tokens[1];
                q.level = tokens[2];
                q.cpu_limit = std::stoi(tokens[3]);
                q.mem_limit = std::stoi(tokens[4]);

                std::string question_path = default_questions_path + q.number + '/';
                FileUtil::readFile(question_path + "desc.txt", q.desc, true);
                FileUtil::readFile(question_path + "header.cpp", q.header, true);
                FileUtil::readFile(question_path + "tail.cpp", q.tail, true);

                _questions_map.insert(std::make_pair(q.number, q));
            }

            in.close();

            LOG(JsonMuduoRPC::INFO, "题库加载成功");
            return true;
        }

    private:
        std::unordered_map<std::string, Question> _questions_map; // 题目编号-题目属性
    };
#endif

} // namespace oj_model
